﻿/**
 * jQuery Plugin mk (v0.3)
 * 
 * Copyright (c) Chris LaVoie (http://thelavoies.net/mkdemo.html)
 *
 * Dual licensed under the MIT and GPL licenses.
 *    MIT license: http://www.opensource.org/licenses/mit-license.php
 *    GPL license: http://www.opensource.org/licenses/gpl-3.0.html
 *
 * Summary:
 *    The mk plugin creates DOM elements wrapped as jQuery objects. The mk
 *    plugin also creates alias functions at the jQuery level for making
 *    elements, and has limited support for element creation templates.
 *    
 * Usage:
 *    $.mk(element, [attributes])
 * 
 * Parameters:
 *    element - the string containing the name of the element to create
 *              (for example: 'a' for <a>, 'div' for a <div>, etc.).
 *
 *              The exceptions to this are for input elements. The following
 *              element names have special meaning to mk, they create <input/>
 *              elements with the value of the "type" attribute set to
 *              element:
 *                        button
 *                        checkbox
 *                        file 
 *                        hidden
 *                        image
 *                        password
 *                        radio
 *                        reset
 *                        submit
 *                        text
 *
 *    attributes - an object containing attribute names and values which the 
 *                 created element will have upon instantiation.
 *
 * Returns:
 *    A new node of the element specified wrapped as a jQuery object.
 *
 * Examples:
 *    var anchor = $.mk('a'); // an empty <a> element wrapped as a jQuery
 *                            // object.
 *        anchor = $('<a/>'); // equivalent to previous line
 *        anchor = $.mka();   // equivalent to previous two lines
 *
 *    var hidden = $('<input type="hidden"/>');
 *        hidden = $.mk('hidden');             // equivalent to previous line
 *        hidden = $.mkhidden();               // equivalent to previous lines
 */
(function($){
  /****************************************************************************
   *                                MK DATA                                   *
   ***************************************************************************/
  // the initial list of known mk aliases and templates
  var templates = { 'a':          {},
                  'abbr':       {},
                  'acronym':    {},
                  'address':    {},
                  'applet':     {},
                  'area':       {},
                  'b':          {},
                  'base':       {},
                  'basefont':   {},
                  'bdo':        {},
                  'big':        {},
                  'blockquote': {},
                  'body':       {},
                  'br':         {},
                  'button':     {element:'input', attributes:{type:'button'}},
                  'caption':    {},
                  'center':     {},
                  'checkbox':   {element:'input', attributes:{type:'checkbox'}},
                  'cite':       {},
                  'code':       {},
                  'col':        {},
                  'colgroup':   {},
                  'dd':         {},
                  'del':        {},
                  'dfn':        {},
                  'dir':        {},
                  'div':        {},
                  'dl':         {},
                  'dt':         {},
                  'em':         {},
                  'fieldset':   {},
                  'file':       {element:'input', attributes:{type:'file'}},
                  'font':       {},
                  'form':       {},
                  'frame':      {},
                  'frameset':   {},
                  'h1':         {},
                  'h2':         {},
                  'h3':         {},
                  'h4':         {},
                  'h5':         {},
                  'h6':         {},
                  'head':       {},
                  'hidden':     {element:'input', attributes:{type:'hidden'}},
                  'hr':         {},
                  'html':       {},
                  'i':          {},
                  'iframe':     {},
                  'image':      {element:'input', attributes:{type:'image'}},
                  'img':        {},
                  'input':      {},
                  'ins':        {},
                  'isindex':    {},
                  'kbd':        {},
                  'label':      {},
                  'legend':     {},
                  'li':         {},
                  'link':       {},
                  'map':        {},
                  'menu':       {},
                  'meta':       {},
                  'noframes':   {},
                  'noscript':   {},
                  'object':     {},
                  'ol':         {},
                  'optgroup':   {},
                  'option':     {},
                  'p':          {},
                  'param':      {},
                  'password':   {element:'input', attributes:{type:'password'}},
                  'pre':        {},
                  'q':          {},
                  'radio':      {element:'input', attributes:{type:'radio'}},
                  'reset':      {element:'input', attributes:{type:'reset'}},
                  's':          {},
                  'samp':       {},
                  'script':     {},
                  'select':     {},
                  'small':      {},
                  'span':       {},
                  'strike':     {},
                  'strong':     {},
                  'style':      {},
                  'sub':        {},
                  'submit':     {element:'input', attributes:{type:'submit'}},
                  'sup':        {},
                  'table':      {},
                  'tbody':      {},
                  'td':         {},
                  'text':       {element:'input', attributes:{type:'text'}},
                  'textarea':   {},
                  'tfoot':      {},
                  'th':         {},
                  'thead':      {},
                  'title':      {},
                  'tr':         {},
                  'tt':         {},
                  'u':          {},
                  'ul':         {},
                  'var':        {}
  };  
  var reserved      = ['each', 'get', 'prefix', 'reserved', 'set'];
  var prefix        = 'mk';
  var prefixPattern = /([$_a-zA-Z])(\1|\d)*/;
  var warn          = (window.console && window.console.warn) || function(){};

  function error (message) {
    if(typeof message === 'string') {
      message = 'jquery.mk: ' + message;
      warn(message);
    }
    if(mk.throw)
      throw message;
    return false;
  }

  function imk() {
    var child = $.mk.apply(this, arguments);
    // if(typeof action == 'function')
      // action.call(this, child, element, attributes, action);
//     else
    for(var index = 0; index < child.length; index++)
      this.appendChild(child[index]);
    return child;
  }

  function imap (args) {
    return function(){return imk.apply(this, args)};
  }

  function alias(templateName) {
    if(typeof templateName !== 'string')
      return error('Template names must be strings');
    if(mk.reserved(templateName))
      return error("'" + template + "' is a reserved template name");

    $.fn[prefix + templateName] = function() {
      var args = arguments;
      args.unshift(templateName);
      return this.map(imap(args));
    };

    return mk[templateName] = function(){return mk(templateName, arguments[0])};
  };

  /****************************************************************************
   *                              MK FUNCTIONS                                *
   ***************************************************************************/
  // the main mk function
  function mk (element) {
    var second = arguments[1],
        third  = arguments[2],
        fourth = arguments[3],
        fifth  = arguments[4],
        attr   = {},
        text   = '',
        count  = 1;
    switch(arguments.length){
      case 5:
        if(typeof second === 'string')
          attr[second] = third;
        if(typeof fourth === 'string')
          text = fourth;
        if(typeof fifth === 'number')
          count = fifth;
        break;
      case 4:
        if(typeof second === 'string')
          attr[second] = third;
        if(typeof fourth === 'string')
          text = fourth;
        else if(typeof fourth === 'number')
          count = fourth;
        break;
      case 3:
        if(typeof second === 'object')
          attr = second;
        if(typeof third === 'string')
          text = third;
        if(typeof fourth === 'number')
          count = fourth;
        break;
      case 2:
        if(typeof second === 'object')
          attr = second;
        if(typeof second === 'string')
          text = second;
        if(typeof second === 'number')
          count = second;
        break;
      default:
        break;
    }
    var options    = templates[element],
        mkel       = function(){return document.createElement(options         && 
                                                              options.element ? options.element :
                                                                                element);},
        newElement = count > 1 ? new Array(count) : mkel(),
        index;
    if(count > 1)
      for(index = 0; index < count; index++)
        newElement[index] = mkel();
    var $newElement = $(newElement),
        attributes  = $.extend({}, 
                               (typeof options.attributes === 'object') ? options.attributes : {},
                               attr),
        key;
    for(key in attributes) {
      // if setting the 'type' attribute of the element use setAttribute() since
      // jQuery will raise an exception if using attr()
      if(key==='type') {
        if(count > 1)
          for(index = 0; index < count; index++)
            newElement[index].setAttribute(key, attributes[key]);
        else
          newElement.setAttribute(key,attributes[key]);
      }
      else
        $newElement.attr(key,attributes[key])
    }
    if(text)
      $newElement.text(text)
    return $newElement;
  };

  /**
   * Summary:
   *    Gets information about a specific $.mk template, or all $.mk templates.
   *
   * Usage:
   *    $.mk.get([templateName])
   *
   * Parameters:
   *    templateName - (optional) the name of the template to retrieve.
   *
   * Returns:
   *    An object containing the details of $.mk template, or undefined
   *    if a template with the specified name does not exist. If 
   *    templateName is not specified an object is returned containing
   *    all $.mk templates.
   */
  mk.get = function() {
    var template = arguments[0];
    if(template)
      return templates[template] ? $.extend(true,{},templates[template]) :
                                   false;
    return $.extend(true, {}, templates);
  };
  
  /**
   * Summary:
   *    The $.mk.set function allows creation or modification of $.mk
   *    element templates.
   *
   * Usage:
   *    $.mk.set(templateName, [elementName], [initialAttributes])
   *
   * Parameters:
   *    templateName      - the string containing the name of the template
   *
   *    elementName       - (optional) the string containing the name of the element
   *                        to create. If this parameter is not supplied the value 
   *                        of templateName is used in its place.
   *
   *    initialAttributes - (optional) an object containing all the initial
   *                        attributes the element will be created with.
   *
   * Returns:
   *    true if the template was set successfully, false otherwise.
   *
   * Examples:
   *    $.mk.set('a', {class:'highlight'});
   *    $.mk('a');                          // <a class="highlight" /> wrapped 
   *                                        // as a jQuery object
   *    $.mka();                            // equivalent to previosu line
   *
   *    $.mk.set('foo', 'form', {method: 'post'});
   *    $.mk('foo');                                // <form method="post" />
   *                                                // wrapped as jQuery object
   *    $.mkfoo();                                  // equivalent to previous line
   */
  mk.set = function(templateName) {
    if(typeof templateName !== 'string')
      return error('Template names must be strings');
    if(mk.reserved(templateName))
      return error("'" + templateName + "' is a reserved template name");

    var template = {};
    if(typeof arguments[1] === 'string')
      template.element = arguments[1];
    if(typeof arguments[1] === 'object')
      template.attributes = arguments[1];
    if(typeof arguments[2] === 'object')
      template.attributes = arguments[2];

    templates[templateName] = template;
    alias(templateName);
    return true;
  };


  mk.prefix = function() {
    var newPrefix = arguments[0];
    if((typeof newPrefix === 'string') && 
       prefixPattern.test(newPrefix)) {
      mk.each(function(tag){
        $.fn[newPrefix + tag] = $.fn[prefix + tag];
        delete $.fn[prefix + tag];
      });
      prefix = newPrefix;
    }
    return prefix;
  };

  mk.reserved =  function (name) {
    return reserved.indexOf(name) != -1;
  };

  mk.each = function (action) {
    if(typeof action !== 'function')
      return error("Required parameter 'action' is not a function");

    var collect = [],
        tag;
    for(tag in templates)
      collect.push(action.call(this, tag, $.extend(true, {}, templates[tag]), action));

    return collect;
  }

  mk.throw = true;

  /****************************************************************************
   *                            ADD MK TO JQUERY                              *
   ***************************************************************************/
  $.extend({mk: mk});
  $.fn.mk = function(element) {
    var args = arguments;
    args.unshift(element);
    return this.map(imap(args));
  };
  $.each(templates, alias);
})(jQuery);
